package com.example.thirdannotation.y2022.aop.aspect;

import com.alibaba.fastjson.JSON;
import com.example.thirdannotation.y2022.aop.annotation.SysLog;
import com.example.thirdannotation.y2022.aop.model.SysLogBO;
import com.example.thirdannotation.y2022.aop.service.SysLogService;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import java.lang.reflect.Method;
import java.text.SimpleDateFormat;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

/**
 * 系统日志切面
 */
@Aspect  // 使用@Aspect注解声明一个切面
@Component
public class SysLogAspect {

    @Autowired
    private SysLogService sysLogService;
    /**
     *   总结：类初始化调用顺序：
     * （1）构造方法Constructor
     * （2）@Autowired
     * （3）@PostConstruct
     */
    @PostConstruct
    private void doInit(){
        System.out.println("doInit");
    }
    /**
     * 当方法符合切点规则不符合环绕通知的规则时候，执行的顺序如下
     * @Before→@After→@AfterRunning(如果有异常→@AfterThrowing)
     *
     * 当方法符合切点规则并且符合环绕通知的规则时候，执行的顺序如下
     * @Around→@Before→@Around→@After执行 ProceedingJoinPoint.proceed() 之后的操作→@AfterRunning(如果有异常→@AfterThrowing)
     */

    /**
     * 这里我们使用注解的形式
     * 当然，我们也可以通过切点表达式直接指定需要拦截的package,需要拦截的class 以及 method
     * 切点表达式:   execution(...)
     *
     * execution(public * *(..)) 任意的公共方法
     * execution（* set*（..）） 以set开头的所有的方法
     * execution（* com.LoggerApply.*（..））com.LoggerApply这个类里的所有的方法
     * execution（* com.annotation.*.*（..））com.annotation包下的所有的类的所有的方法
     * execution（* com.annotation..*.*（..））com.annotation包及子包下所有的类的所有的方法
     * execution(* com.annotation..*.*(String,?,Long)) com.annotation包及子包下所有的类的有三个参数，第一个参数为String类型，第二个参数为任意类型，第三个参数为Long类型的方法
     * execution(@annotation(com.lingyejun.annotation.Lingyejun))
     */
    @Pointcut("@annotation(com.example.thirdannotation.y2022.aop.annotation.SysLog)")
    public void logPointCut() {}

    @Before(value = "logPointCut()")
    public void before(){
        System.out.println(System.currentTimeMillis());
        System.out.println("before:"+ LocalDateTime.now().getSecond());
    }

    @After(value = "logPointCut()")
    public void after(){
        System.out.println(System.currentTimeMillis());
        System.out.println("after:"+ LocalDateTime.now().getSecond());
    }

    /**
     * 环绕通知 @Around  ， 当然也可以使用 @Before (前置通知)  @After (后置通知)
     * @param point
     * @return
     * @throws Throwable
     */
    @Around("logPointCut()")
    public Object around(ProceedingJoinPoint point) throws Throwable {
        System.out.println("around1");
        long beginTime = System.currentTimeMillis();
        /**
         * 环绕通知 = 前置 + 目标方法执行 + 后置，proceed方法就是用于启动目标方法执行的
         * Proceedingjoinpoint 继承了 JoinPoint。是在JoinPoint的基础上暴露出 proceed 这个方法。proceed很重要，这个是aop代理链执行的方法。
         * 暴露出这个方法，就能支持 aop:around 这种切面（而其他的几种切面只需要用到JoinPoint，，这也是环绕通知和前置、后置通知方法的一个最大区别。这跟切面类型有关）
         * */
        Object result = point.proceed();
        long time = System.currentTimeMillis() - beginTime;
        try {
            saveLog(point, time);
        } catch (Exception e) {
        }
        System.out.println("around2");
        return result;
    }

    /**
     * Object 的参数名称必须与returning的value一致
     * @param joinPoint
     * @param abc
     */
    @AfterReturning(value = "logPointCut()", returning = "abc")
    public void doAfterReturning(JoinPoint joinPoint, Object abc){
        System.out.println("doAfterReturning");
        System.out.println("abc:"+abc);
    }

    /**
     * 保存日志
     */
    private void saveLog(ProceedingJoinPoint joinPoint, long time) {
        System.out.println("saveLog");
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        SysLogBO sysLogBO = new SysLogBO();
        sysLogBO.setExeuTime(time);
        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
        sysLogBO.setCreateDate(dateFormat.format(new Date()));
        SysLog sysLog = method.getAnnotation(SysLog.class);
        if(sysLog != null){
            //注解上的描述
            sysLogBO.setRemark(sysLog.value());
        }
        //请求的 类名、方法名
        String className = joinPoint.getTarget().getClass().getName();
        String methodName = signature.getName();
        sysLogBO.setClassName(className);
        sysLogBO.setMethodName(methodName);
        //请求的参数
        Object[] args = joinPoint.getArgs();
        try{
            List<String> list = new ArrayList<String>();
            for (Object o : args) {
                list.add(JSON.toJSONString(o));
            }
            sysLogBO.setParams(list.toString());
        }catch (Exception e){ }
        sysLogService.save(sysLogBO);
    }
}
